import { DataManager } from "@/managers/DataManager.js";
import { clamp } from "@/utils/number.js";
import { contains } from "@/utils/Array.js";
import { Controller } from "@/controller/Controller.js";
import { trait_type,special_flag,icon_flag} from "@/const/trait.js";

export class Game_BattlerBase extends Controller {
    constructor() {
        super();
        this._hp = 1;
        this._mp = 0;
        this._tp = 0;
        this._hidden = false;
        this.clearParamPlus();
        this.clearStates();
        this.clearBuffs();
    }

    // Hit Points
    get hp() { return this._hp; }

    // Magic Points
    get mp() { return this._mp; }

    // Tactical Points
    get tp() { return this._tp; }

    // Maximum Hit Points
    get mhp() { return this.param(0); }

    // Maximum Magic Points
    get mmp() { return this.param(1); }

    // ATtacK power
    get atk() { return this.param(2); }

    // DEFense power
    get def() { return this.param(3); }

    // Magic ATtack power
    get mat() { return this.param(4); }

    // Magic DeFense power
    get mdf() { return this.param(5); }

    // AGIlity
    get agi() { return this.param(6); }

    // LUcK
    get luk() { return this.param(7); }

    // HIT rate
    get hit() { return this.xparam(0); }

    // EVAsion rate
    get eva() { return this.xparam(1); }

    // CRItical rate
    get cri() { return this.xparam(2); }

    // Critical EVasion rate
    get cev() { return this.xparam(3); }

    // Magic EVasion rate
    get mev() { return this.xparam(4); }

    // Magic ReFlection rate
    get mrf() { return this.xparam(5); }

    // CouNTer attack rate
    get cnt() { return this.xparam(6); }

    // Hp ReGeneration rate
    get hrg() { return this.xparam(7); }

    // Mp ReGeneration rate
    get mrg() { return this.xparam(8); }

    // Tp ReGeneration rate
    get trg() { return this.xparam(9); }

    // TarGet Rate
    get tgr() { return this.sparam(0); }

    // GuaRD effect rate
    get grd() { return this.sparam(1); }

    // RECovery effect rate
    get rec() { return this.sparam(2); }

    // PHArmacology
    get pha() { return this.sparam(3); }

    // Mp Cost Rate
    get mcr() { return this.sparam(4); }

    // Tp Charge Rate
    get tcr() { return this.sparam(5); }

    // Physical Damage Rate
    get pdr() { return this.sparam(6); }

    // Magical Damage Rate
    get mdr() { return this.sparam(7); }

    // Floor Damage Rate
    get fdr() { return this.sparam(8); }

    // EXperience Rate
    get exr() { return this.sparam(9); }

    clearParamPlus() {
        this._paramPlus = [0, 0, 0, 0, 0, 0, 0, 0];
    }

    clearStates() {
        this._states = [];
        this._stateTurns = {};
        //this.emit('state-clear');
    }

    eraseState(stateId) {
        let index = this._states.indexOf(stateId);
        if (index >= 0) {
            this._states.splice(index, 1);
            delete this._stateTurns[stateId];
            //this.emit('state-erase',stateId);
        }
    }

    isStateAffected(stateId) {return contains(this._states, stateId);}
    isDeathStateAffected() {return this.isStateAffected(this.deathStateId());}
    deathStateId() {return 1;}
    isStateExpired(stateId) {return this._stateTurns[stateId] == 0;}

    resetStateCounts(stateId) {
        let state = $dataStates[stateId];
        let variance = 1 + Math.max(state.maxTurns - state.minTurns, 0);
        let oldTurn=this._stateTurns[stateId];
        this._stateTurns[stateId] = state.minTurns + Math.floor(Math.random()*variance);
        //this.emit('state-changed',stateId,this._stateTurns[stateId],oldTurn);
    } 

    updateStateTurns() {
        let node = this.find('state-changed');
        for(let stateId of this._states){
            if(this._stateTurns[stateId]>0){
                --this._stateTurns[stateId];
                let turns=this._stateTurns[stateId];
                node?.handler?.(...node.args,turns,turns+1);
            }
        }
    }

    clearBuffs() {
        this._buffs = [0, 0, 0, 0, 0, 0, 0, 0];
        this._buffTurns = [0, 0, 0, 0, 0, 0, 0, 0];
        //this.emit('buff-clear');
    }

    eraseBuff(paramId) {
        this._buffs[paramId] = 0;
        this._buffTurns[paramId] = 0;
        //this.emit('buff-erase',paramId);
    }

    buffLength() {
        return this._buffs.length;
    }

    buff(paramId) {
        return this._buffs[paramId];
    }

    isBuffAffected(paramId) {
        return this._buffs[paramId] > 0;
    }

    isDebuffAffected(paramId) {
        return this._buffs[paramId] < 0;
    }

    isBuffOrDebuffAffected(paramId) {
        return this._buffs[paramId] !== 0;
    }

    isMaxBuffAffected(paramId) {
        return this._buffs[paramId] === 2;
    }

    isMaxDebuffAffected(paramId) {
        return this._buffs[paramId] === -2;
    }

    increaseBuff(paramId) {
        if (!this.isMaxBuffAffected(paramId)) {
            this._buffs[paramId]++;
            let buff=this._buffs[paramId];
            //this.emit('buff-changed','value',paramId,buff,buff-1);
        }
    }

    decreaseBuff(paramId) {
        if (!this.isMaxDebuffAffected(paramId)) {
            this._buffs[paramId]--;
            let buff=this._buffs[paramId];
            //this.emit('buff-changed','value',paramId,buff,buff+1);
        }
    }

    overwriteBuffTurns(paramId, turns) {
        if (this._buffTurns[paramId] < turns) {
            let oldTurns=this._buffTurns[paramId];
            this._buffTurns[paramId] = turns;
            //this.emit('buff-changed','turn',paramId,turns,oldTurns);
        }
    }

    isBuffExpired(paramId) {
        return this._buffTurns[paramId] === 0;
    }

    updateBuffTurns() {
        let node=this.find('buff-changed');
        for (let i = 0; i < this._buffTurns.length; i++) {
            if (this._buffTurns[i] > 0) {
                this._buffTurns[i]--;
                let turns=this._buffTurns[i];
                node?.handler?.('turn',...node.args,turns,turns+1);
            }
        }
    }

    die() {
        this._hp = 0;
        this.clearStates();
        this.clearBuffs();
        //this.emit('die');
    }

    revive() {
        if (this._hp == 0) {
            this._hp = 1;
            //this.emit('revive');
        }
    }

    states() {return this._states.map(id=>$dataStates[id]);}

    stateIcons() {
        let iconIndexs=[];
        let states=this.states();
        for(let state of states){
            if(state.iconIndex>0){
                iconIndexs.push(state.iconIndex);
            }
        }
        return iconIndexs;
    }

    buffIcons() {
        let icons = [];
        for (let i = 0; i < this._buffs.length; i++) {
            if (this._buffs[i] !== 0) {
                icons.push(this.buffIconIndex(this._buffs[i], i));
            }
        }
        return icons;
    }

    buffIconIndex(buffLevel, paramId) {
        if (buffLevel > 0) {
            return icon_flag.buff_start + (buffLevel - 1) * 8 + paramId;
        } else if (buffLevel < 0) {
            return icon_flag.debuff_start + (-buffLevel - 1) * 8 + paramId;
        } else {
            return 0;
        }
    }

    allIcons() {return this.stateIcons().concat(this.buffIcons());}
    traitObjects() {return this.states();}
    allTraits() {return this.traitObjects().reduce((r,obj)=>r.concat(obj.traits),[]);}
    traits(code) {return this.allTraits().filter(trait=>trait.code == code);}
    traitsWithId(code, id) {return this.allTraits().filter(trait=>trait.code == code && trait.dataId == id);}
    traitsPi(code, id) {return this.traitsWithId(code, id).reduce((r, trait)=>r * trait.value, 1);}
    traitsSum(code, id) {return this.traitsWithId(code, id).reduce((r, trait) =>r + trait.value, 0);}
    traitsSumAll(code) {return this.traits(code).reduce((r, trait)=> r + trait.value, 0);}
    traitsSet(code) {return this.traits(code).reduce((r, trait)=>r.concat(trait.dataId), []);}
    paramBase(paramId) {return 0;}
    paramPlus(paramId) {return this._paramPlus[paramId];}

    paramMin(paramId) {
        if (paramId === 1) {
            return 0;   // MMP
        } else {
            return 1;
        }
    }

    paramMax(paramId) {
        if (paramId === 0) {
            return 999999;  // MHP
        } else if (paramId === 1) {
            return 9999;    // MMP
        } else {
            return 999;
        }
    }

    paramRate(paramId) {return this.traitsPi(trait_type.param, paramId);}
    paramBuffRate(paramId) {return this._buffs[paramId] * 0.25 + 1.0;}

    param(paramId) {
        let value = this.paramBase(paramId) + this.paramPlus(paramId);
        value *= this.paramRate(paramId) * this.paramBuffRate(paramId);
        return Math.round(clamp(value, this.paramMin(paramId), this.paramMax(paramId)));
    }

    xparam(xparamId) {return this.traitsSum(trait_type.xparam, xparamId);}
    sparam(sparamId) {return this.traitsPi(trait_type.sparam, sparamId);}
    elementRate(elementId) {return this.traitsPi(trait_type.element_rate, elementId);}
    debuffRate(paramId) {return this.traitsPi(trait_type.debuff_rate, paramId);}
    stateRate(stateId) {return this.traitsPi(trait_type.state_rate, stateId);}
    stateResistSet() {return this.traitsSet(trait_type.state_resist);}
    isStateResist(stateId) {return contains(this.stateResistSet(), stateId);}
    attackElements() {return this.traitsSet(trait_type.attack_element);}
    attackStates() {return this.traitsSet(trait_type.attack_state);}
    attackStatesRate(stateId) {return this.traitsSum(trait_type.attack_state, stateId);}
    attackSpeed() {return this.traitsSumAll(trait_type.attack_speed);}
    attackTimesAdd() {return Math.max(this.traitsSumAll(trait_type.attack_times), 0);}
    addedSkillTypes() {return this.traitsSet(trait_type.stype_add);}
    isSkillTypeSealed(stypeId) {return contains(this.traitsSet(trait_type.stype_seal), stypeId);}
    addedSkills() {return this.traitsSet(trait_type.skill_add);}
    isSkillSealed(skillId) {return contains(this.traitsSet(trait_type.skill_seal), skillId);}
    isEquipWtypeOk(wtypeId) {return contains(this.traitsSet(trait_type.equip_wtype), wtypeId);}
    isEquipAtypeOk(atypeId) {return contains(this.traitsSet(trait_type.equip_atype), atypeId);}
    isEquipTypeLocked(etypeId) {return contains(this.traitsSet(trait_type.equip_lock), etypeId);}
    isEquipTypeSealed(etypeId) {return contains(this.traitsSet(trait_type.equip_seal), etypeId);}
    slotType() {return Math.max(...this.traitsSet(trait_type.slot_type))??0;}
    isDualWield() {return this.slotType() == 1;}
    actionPlusSet() {return this.traits(trait_type.action_plus).map(trait=>trait.value);}
    specialFlag(flagId) {return this.traits(trait_type.special_flag).some(trait=>trait.dataId == flagId);}
    collapseType() {return Math.max(...this.traitsSet(trait_type.collapse_type))??0;}
    partyAbility(abilityId) {return this.traits(trait_type.party_ability).some(trait=>trait.dataId == abilityId);}
    isAutoBattle() {return this.specialFlag(special_flag.auto_battle);}
    isGuard() {return this.specialFlag(special_flag.id_guard) && this.canMove();}
    isSubstitute() {return this.specialFlag(special_flag.substitute) && this.canMove();}
    isPreserveTp() {return this.specialFlag(special_flag.preserve_tp);}
    
    addParam(paramId, value) {
        this._paramPlus[paramId] += value;
        this.refresh();
    }

    setHp(hp) {
        let oldhp=this._hp;
        this._hp = clamp(hp, 0, this.mhp);
        if(oldhp!=this._hp){
            //this.emit('hp-changed',this._hp,oldhp);
        }
    }

    setMp(mp) {
        let oldmp=this._mp;
        this._mp = clamp(mp, 0, this.mmp);
        if(oldmp!=this._mp){
            //this.emit('mp-changed',this._mp,oldmp);
        }
    }

    setTp(tp) {
        let oldtp=this._tp;
        this._tp = clamp(tp, 0, this.maxTp());
        if(oldtp!=this._tp){
            //this.emit('tp-changed',this._tp,oldtp);
        }
    }

    maxTp() {return 100;}
    refresh() {}

    recoverAll() {
        this.clearStates();
        this._hp = this.mhp;
        this._mp = this.mmp;
    }

    hpRate() {return this.hp / this.mhp;}
    mpRate() {return this.mmp > 0 ? this.mp / this.mmp : 0;}
    tpRate() {return this.tp / this.maxTp();}

    hide() {
        this._hidden = true;
        //this.emit('hide');
    }

    appear() {
        this._hidden = false;
        //this.emit('appear');
    }

    isHidden() {return this._hidden;}
    isAppeared() {return !this.isHidden();}
    isDead() {return this.isAppeared() && this.isDeathStateAffected();}
    isAlive() {return this.isAppeared() && !this.isDeathStateAffected();}
    isDying() {return this.isAlive() && this._hp < this.mhp / 4;}
    isRestricted() {return this.isAppeared() && this.restriction() > 0;}
    canInput() {return this.isAppeared() && !this.isRestricted() && !this.isAutoBattle();}
    canMove() {return this.isAppeared() && this.restriction() < 4;}
    isConfused() {return this.isAppeared() && this.restriction() >= 1 && this.restriction() <= 3;}
    confusionLevel() {return this.isConfused() ? this.restriction() : 0;}
    isActor() {return false;}
    isEnemy() {return false;}

    sortStates() {
        this._states.sort(function (a, b) {
            let p1 = $dataStates[a].priority;
            let p2 = $dataStates[b].priority;
            if (p1 !== p2) {
                return p2 - p1;
            }
            return a - b;
        });
    }

    restriction() {
        return Math.max.apply(null, this.states().map(function (state) {
            return state.restriction;
        }).concat(0));
    }

    addNewState(stateId) {
        if (stateId == this.deathStateId()) {
            this.die();
        }
        let restricted = this.isRestricted();
        this._states.push(stateId);
        this.sortStates();
        //this.emit('state-add',stateId);
        if (!restricted && this.isRestricted()) {
            this.onRestrict();
        }
    }

    onRestrict() {}

    mostImportantStateText() {
        let states = this.states();
        for (let i = 0; i < states.length; i++) {
            if (states[i].message3) {
                return states[i].message3;
            }
        }
        return '';
    }

    stateMotionIndex() {
        let states = this.states();
        if (states.length > 0) {
            return states[0].motion;
        } else {
            return 0;
        }
    }

    stateOverlayIndex() {
        let states = this.states();
        if (states.length > 0) {
            return states[0].overlay;
        } else {
            return 0;
        }
    }

    isSkillWtypeOk(skill) {return true;}
    skillMpCost(skill) {return Math.floor(skill.mpCost * this.mcr);}
    skillTpCost(skill) {return skill.tpCost;}
    canPaySkillCost(skill) {return this._tp >= this.skillTpCost(skill) && this._mp >= this.skillMpCost(skill);}

    paySkillCost(skill) {
        this._mp -= this.skillMpCost(skill);
        this._tp -= this.skillTpCost(skill);
    }

    isOccasionOk(item) {
        if ($gameParty.inBattle()) {
            return item.occasion === 0 || item.occasion === 1;
        } else {
            return item.occasion === 0 || item.occasion === 2;
        }
    }

    meetsUsableItemConditions(item) {return this.canMove() && this.isOccasionOk(item);}

    meetsSkillConditions(skill) {
        return (this.meetsUsableItemConditions(skill) &&
            this.isSkillWtypeOk(skill) && this.canPaySkillCost(skill) &&
            !this.isSkillSealed(skill.id) && !this.isSkillTypeSealed(skill.stypeId));
    }

    meetsItemConditions(item) {return this.meetsUsableItemConditions(item) && $gameParty.hasItem(item);}

    canUse(item) {
        if (!item) {
            return false;
        } else if (DataManager.isSkill(item)) {
            return this.meetsSkillConditions(item);
        } else if (DataManager.isItem(item)) {
            return this.meetsItemConditions(item);
        } else {
            return false;
        }
    }

    canEquip(item) {
        if (!item) {
            return false;
        } else if (DataManager.isWeapon(item)) {
            return this.canEquipWeapon(item);
        } else if (DataManager.isArmor(item)) {
            return this.canEquipArmor(item);
        } else {
            return false;
        }
    }

    canEquipWeapon(item) {return this.isEquipWtypeOk(item.wtypeId) && !this.isEquipTypeSealed(item.etypeId);}
    canEquipArmor(item) {return this.isEquipAtypeOk(item.atypeId) && !this.isEquipTypeSealed(item.etypeId);}
    attackSkillId() {return 1;}
    guardSkillId() {return 2;}
    canAttack() {return this.canUse($dataSkills[this.attackSkillId()]);}
    canGuard() {return this.canUse($dataSkills[this.guardSkillId()]);}

    serialize() {
        return {
            hp: this._hp,
            mp: this._mp,
            tp: this._tp,
            hidden: this._hidden,
            states:this._states,
            stateTurns:this._stateTurns,
            buffs:this._buffs,
            buffTurns:this._buffTurns,
            paramPlus:this._paramPlus
        };
    }

    deserialize(data){
        if(!data)return;
        this._hp=data.hp;
        this._mp=data.mp;
        this._tp=data.tp;
        this._hidden=data.hidden;
        this._states=data.states;
        this._stateTurns=data.stateTurns;
        this._buffs=data.buffs;
        this._buffTurns=data.buffTurns;
        this._paramPlus=data.paramPlus;
    }
}